using System.Collections.Generic;
using System.Linq;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * A holder for builtins that operate on TemplateNodeModels.
     */
    internal abstract class NodeBuiltins
    {
        internal abstract class NodeBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (!(model is ITemplateNodeModel))
                {
                    throw InvalidTypeException(model, Target, env, "node model");
                }
                return CalculateResult((ITemplateNodeModel) model, env);
            }

            protected abstract ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env);
        }

        internal class AncestorsBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                var result = new AncestorSequence(env);
                ITemplateNodeModel parent = nodeModel.GetParentNode();
                while (parent != null)
                {
                    result.Add(parent);
                    parent = parent.GetParentNode();
                }
                return result;
            }
        }

        internal class ChildrenBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                return nodeModel.GetChildNodes();
            }
        }


        internal class NodeNameBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                return new SimpleScalar(nodeModel.GetNodeName());
            }
        }

        internal class NodeTypeBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                return new SimpleScalar(nodeModel.GetNodeType());
            }
        }

        internal class ParentBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                return nodeModel.GetParentNode();
            }
        }

        internal class RootBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                ITemplateNodeModel result = nodeModel;
                ITemplateNodeModel parent = nodeModel.GetParentNode();
                while (parent != null)
                {
                    result = parent;
                    parent = result.GetParentNode();
                }
                return result;
            }
        }

        internal class NodeNamespaceBuiltIn : NodeBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateNodeModel nodeModel, Environment env)
            {
                string nsUri = nodeModel.GetNodeNamespace();
                return nsUri == null ? null : new SimpleScalar(nsUri);
            }
        }

        internal class AncestorSequence : SimpleSequence, ITemplateMethodModel
        {
            private readonly Environment _env;

            internal AncestorSequence(Environment env)
            {
                _env = env;
            }

            public object Exec(IList<string> names)
            {
                if (names == null || names.Count == 0)
                {
                    return this;
                }
                var result = new AncestorSequence(_env);
                for (int i = 0; i < Size(); i++)
                {
                    var tnm = (ITemplateNodeModel)Get(i);
                    string nodeName = tnm.GetNodeName();
                    string nsUri = tnm.GetNodeNamespace();
                    if (nsUri == null)
                    {
                        if (names.Contains(nodeName))
                        {
                            result.Add(tnm);
                        }
                    }
                    else
                    {
                        if (names.Any(name => StringUtil.MatchesName(name, nodeName, nsUri, _env)))
                        {
                            result.Add(tnm);
                        }
                    }
                }
                return result;
            }
        }
    }
}